{
  // 防抖
  // 防抖函数原理：
  // 在事件被触发戩秒后再执行回调，如果在这戩秒内又被触发，则重新计时。
  // 适用场景：
  // 1 按钮提交场景：防止多次提交按钮，只执行最后提交的一次
  // 2 服务端验证场景：表单验证需要服务端配合，只执行一段连续的输入事件的最后一次，还有搜索联想词功能类似
  const debounce = (fn, delay) => {
    let timer = null;
    return function () {
      if (timer) clearTimeout(timer);

      timer = setTimeout(() => {
        fn.call(this, arguments);
      }, delay);
    };
  };

  function handle() {
    console.log(Math.random());
  }

  // window.addEventListener('resize', debounce(handle, 1000))
}
// 防抖是延迟执行，而节流是间隔执行
{
  // 节流
  // 节流函数原理：
  // 规定在一个单位时间内，只能触发一次函数。如果这个单位时间内触发多次函数，只有一次生效。防抖是延迟执行，而节流是间隔执行，函数节流即每隔一段时间就执行一次
  // 适用场景：
  // 拖拽场景：固定时间内只执行一次，防止超高频次触发位置变动
  // 缩放场景：监控浏览器夁攊斔巆巒攊
  // 动画场景：避免短时间内多次触发动画引起性能问题

  // 定时器实现
  const throttle = (fn, delay) => {
    let flag = false;

    return function () {
      if (flag) return;

      flag = true;
      setTimeout(() => {
        fn.call(this, arguments);
        flag = false;
      }, delay);
    };
  };

  // 时间戳实现
  const throttle1 = (fn, delay) => {
    let preTime = Date.now();
    return function () {
      let nowTime = Date.now();
      if (nowTime - preTime >= delay) {
        fn.call(this, arguments);
        preTime = Date.now();
      }
    };
  };

  function handle() {
    console.log(Math.random());
  }

  let fn = throttle(handle, 100);
  fn();
  fn();
}

{
  // 节流函数
  // 1 每隔一段时间，执行一次
  // 2 第一次触发立即执行
  // 3 如果在间隔时间内处罚， 会在间隔末尾在执行一次
  function throttle(callback, ts) {
    let isFirstClick = true;
    let execDate = +new Date();
    let timeout = null;

    let throttleFn = function () {
      if (isFirstClick) {
        // 首次立即执行
        callback();
        isFirstClick = false;
        execDate = +new Date();
      } else {
        let currentDate = +new Date();
        if (currentDate - execDate >= ts) {
          // 每隔一段时间，执行一次函数
          callback();
          execDate = +new Date();
        } else {
          // 间隔时间内触发，在间隔末尾在执行一次
          if (timeout) clearTimeout(timeout);
          const wait = execDate + ts - new Date();

          timeout = setTimeout(() => {
            callback();
            execDate = +new Date();
          }, wait);
        }
      }
    };

    throttleFn.cancel = function () {
      clearTimeout(timeout);
      timeout = null;
    };

    return throttleFn;
  }
}

// 手写call
{
  // 1 将函数设置为对象的属性
  // 2 执行该函数
  // 3 删除该函数

  Function.prototype.myCall = function (context, ...args) {
    context = context || window;
    context.fn = this;
    context.fn(...args);
    delete context.fn;
  };

  let obj = {
    age: 18,
  };

  function say(age, name) {
    console.log(this.age);
    console.log(age);
    console.log(name);
  }

  say.myCall(obj, 20, '小詹子');
}

// 手写apply
{
  Function.prototype.myApply = function (context, argsArr) {
    context = context || window;
    context.fn = this;
    context.fn(...argsArr);
    delete context.fn;
  };
}

{
  // 手写bind
  // 创建了一个新的函数，并且它第一个参数被指定为这个新函数的this
  Function.prototype.myBind = function (context) {
    context = typeof context === 'object' ? context : window;

    return (...args) => {
      // 可以利用call实现
      return this.call(context, ...args);
    };
  };

  // 验证样例
  function fun(arg1, arg2) {
    console.log(this.name);
    console.log(arg1 + arg2);
  }

  const _this = { name: 'YIYING' };
  // 只变更fun中的this指向，返回新function对象
  const newFun = fun.myBind(_this);
  newFun(1, 2);

  // MDN上提供的bind的polyfill
  if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
      if (typeof this !== 'function') {
        throw new TypeError(
          'Function.prototype.bind - what is trying to be bound is not callable'
        );
      }

      var aArgs = Array.prototype.slice.call(arguments, 1), // 除函数外其他传入的参数
        fToBind = this, // 获取硬绑定的函数
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(
            this instanceof fNOP // 判断绑定函数是否被new调用，如果是的话就会使用新创建的this替换硬绑定的this
              ? this
              : oThis,
            aArgs.concat(Array.prototype.slice.call(arguments))
          );
        }; // 返回的绑定操作函数

      // 维护原型关系
      if (this.prototype) {
        // Function.prototype doesn't have a prototype property
        fNOP.prototype = this.prototype;
      }

      fBound.prototype = new fNOP();

      return fBound;
    };
  }

  // 测试
  function Foo() {
    console.log(this.a);
    this.a = 1;
  }
  Foo.prototype.show = function () {
    console.log(this.a);
  };
  Foo(); // undefined this指向window
  var obj1 = new Foo();
  obj1.show(); // 1 this指向实例，也就是类本身

  var bar = Foo.bind({ a: 2 });
  bar(); // 2 this指向绑定的对象
  var obj2 = new bar(); // bind返回的函数被new，bind不起作用，this指向new出来的实例
  obj2.show(); // 1 bind内部维护了原型关系的继承
}

{
  // 实现new 关键字
  // 1 创建一个对象
  // 2 将对象的隐式原型（__proto__）指向构造函数的原型（prototype）
  // 3 绑定this为该新建的对象
  // 4 返回这个新建的对象

  // 构造函数没有返回值的情况
  function myNew() {
    let obj = {};
    Constructor = Array.prototype.shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj, arguments);
    return obj;
  }

  // 解释
  // 1 创建一个新的对象，并返回。符合new函数的功能。
  // 2 截取传入myNew函数的第一个参数。
  // 3 将第一个参数的prototype与要返回的对象建立关联。
  // 4 使用apply，改变构造函数的this指向，使其指向新对象，这样，obj就可以访问到构造函数中的属性了。
  // 5 返回obj。

  // 构造函数有返回值的情况（通常是没有返回值的）
  function myNew() {
    let obj = {};
    Constructor = Array.prototype.shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    let ret = Constructor.apply(obj, arguments);
    return typeof ret === 'object' ? ret : obj;
  }

  function _new(fn, ...rest) {
    // 基于obj的原型创建一个新的对象
    const obj = Object.create(fn.prototype);
    // 添加属性到新创建的newObj上, 并获取obj函数执行的结果.
    // const result = fn.apply(obj, rest);
    const result = fn.call(obj, ...rest);
    // 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
    return typeof result === 'object' ? result : obj;
  }
}

{
  // new实际上是定义一个具有构造函数内置对象的实例
  // 1 创建js空对象{}
  // 2 将要实例化的对象的原型链指向该对象原型
  // 3 绑定该对象为this的指向
  // 4 返回该对象
  let New = function (fn, ...args) {
    // 创建一个对象，将要实例化对象的原型链指向该对象
    let obj = Object.create(fn.prototype);
    // 将函数指向该对象
    fn.call(obj, ...args);
    // 返回该对象
    return obj;
  };

  function fun1() {
    this.name = '?';
  }
  // 打印两者
  // console.log(New(fun1), new fun1()); // fun1 {name: "?"} fun1 {name: "?"}
  // // 判断两个实例化对象是否全等。
  // console.log(JSON.stringify(New(fun1)) === JSON.stringify(new fun1())); // true

  // es5写法
  function New(fn, ...args) {
    let obj = {}; // 创建一个javascript空对象 {};
    obj.__proto = fn.prototype; // 将要实例化对象的原型链指向该对象的原型
    fn.call(obj, ...args); // 绑定该对象为this指向
    return obj; // 返回该对象
  }
}

{
  // 手写简单版本promise

  // 创建了三个常量用于表示状态
  const PENDING = 'pending';
  const RESOLVED = 'resolved';
  const REJECTED = 'rejected';

  function MyPromise(executor) {
    this.value = null;
    this.state = PENDING;
    this.resolveCbs = [];
    this.rejectCbs = [];

    const resolve = (value) => {
      // setTimeout 模拟 then 的异步
      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = RESOLVED;
          this.value = value;
          this.resolveCbs.map((cb) => cb(value));
        }
      });
    };

    const reject = (value) => {
      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = REJECTED;
          this.value = value;
          this.rejectCbs.map((cb) => cb(value));
        }
      });
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  MyPromise.prototype.then = function (onResolved, onRejected) {
    if (this.state === PENDING) {
      this.resolveCbs.push(onResolved);
      this.rejectCbs.push(onRejected);

      // 模拟promise then链式调用
      // return new MyPromise((resolve, reject) => {
      //   this.resolveCbs.push(() => {
      //     const res = onResolved(this.value);
      //     resolve(res);
      //   });
      // });
    }
    if (this.state === RESOLVED) onResolved(this.value);
    if (this.state === REJECTED) onRejected(this.value);
  };

  // 测试
  new MyPromise((resolve, reject) => {
    console.log('start');
    resolve(1);
  }).then((res) => {
    console.log('then');
    console.log(res);
  });
  console.log('end');
}

{
  // Promise.all()的一个简版实现
  function PromiseAll(promises) {
    return new Promise((resolve, reject) => {
      // 参数判断
      if (!Array.isArray(promises)) {
        throw new TypeError('promises must be an array');
      }

      const result = []; // 存放结果
      let count = 0; // 记录有几个resolved
      promises.forEach((promise, index) => {
        // 用promise包裹一层，确保每一个数据都是promise
        Promise.resolve(promise).then(
          (res) => {
            result[index] = res;
            count++;
            // 判断是否已经完成
            if (count === promises.length) {
              resolve(result);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }

  function test() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(3);
      }, 2000);
    });
  }

  PromiseAll([1, 2, test(), 4, Promise.reject(5)])
    .then((res) => {
      console.log('res: ', res);
    })
    .catch((err) => {
      console.log('err: ', err);
    });
}

{
  // Promise.race()的一个简版实现
  function PromiseRace(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
        // 如果不是Promise实例需要转化为Promise实例
        Promise.resolve(promise)
          .then((res) => {
            resolve(res);
          })
          .catch((err) => {
            reject(err);
          });
      });
    });
  }
}

{
  //Promise.allSettled的一个简版实现
  // 法一
  function promiseAllSettled(promises) {
    return new Promise((resolve, reject) => {
      let result = [];
      let count = 0;
      promises.forEach((promise, index) => {
        // 用promise包裹一层，确保每一个数据都是promise
        Promise.resolve(promise)
          .then(
            (res) => {
              result[index] = { status: 'fulfilled', value: res };
              count++;
            },
            (err) => {
              result[index] = { status: 'rejected', reason: err };
              count++;
            }
          )
          .finally(() => {
            // 这里将结果吐出去
            if (count === promises.length) {
              resolve(result);
            }
          });
      });
    });
  }

  /**
   * 处理promise数组，把每个promise的.then方法返回的promise
   * 不管是resolve还是reject，最终都会执行.then
   * 保证调用promise.all方法时，不管是成功还是失败都抛出去
   */
  function handlePromise(promiseList) {
    return promiseList.map((promise) =>
      promise.then(
        (res) => ({ status: 'fulfilled', value: res }),
        (err) => ({ status: 'rejected', value: err })
      )
    );
  }

  // 法二
  function promiseAllSettled(promises) {
    return new Promise((resolve, reject) => {
      let result = [];
      let count = 0;
      handlePromise(promises).forEach((promise, index) => {
        Promise.resolve(promise).then((res) => {
          result[index] = res;
          count++;
          if (count === promises.length) {
            resolve(result);
          }
        }),
          (err) => {
            reject(err);
          };
      });
    });
  }
  let ps = [Promise.resolve(1), Promise.resolve(2), Promise.reject(3), 4, 5];
  Promise.allSettled(ps).then((res) => {
    console.log(res);
  });
  promiseAllSettled(ps).then((res) => {
    console.log(res);
  });
}

{
  // 介绍instanceof，并手动实现
  // instanceof主要用于判断某个实例是否属于某个类型，或者判断某个实例是其父类型或祖先类型
  // 实现原理就是只要右边变量的prototype在左边变量的原型链上即可。
  // 因此instanceof在查找过程会遍历左边变量的原型链，直到找到右边变量的prototype；
  // 如果查找失败，则返回false

  function myInstanceof(left, right) {
    let leftVal = left.__proto__;
    let rightVal = right.prototype;

    // 若找不到，就一直循环父类或祖先
    while (leftVal) {
      // 查找到原型
      if (leftVal === rightVal) {
        return true;
      }
      // 获取祖先实例
      leftVal = leftVal.__proto__;
    }

    return false;
  }
  console.log(myInstanceof(() => {}, Function));
}

{
  function isObject(obj) {
    return (
      (typeof obj === 'object' || typeof obj === 'function') && obj !== null
    );
  }
  // 简单版本深拷贝(只考虑数组对象)
  function deepClone(obj, hash = new WeakMap()) {
    if (!isObject(obj)) return obj;

    // 防止循环引用
    // 解决循环引用，可以开辟一个存储空间，来存储当前对象和拷贝对象的对应关系
    // 这个存储空间需要可以存储key-value形式的数据，且key可以是一个引用类型 如WeakMap
    if (hash.has(obj)) return hash.get(obj); // 查哈希表
    // 判断目标是数组还是对象
    let newObj = Array.isArray(obj) ? [] : {};
    hash.set(obj, newObj); // 设置哈希表

    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const element = obj[key];
        if (isObject(element)) {
          // 如果是对象继续递归
          newObj[key] = deepClone(element, hash);
        } else {
          // 如果不是赋值即可
          newObj[key] = element;
        }
      }
    }
    return newObj;
  }

  // 深拷贝 考虑所有（函数 正则 日期 数组 对象 Symbol）
  function deepCopy(obj, cache = new WeakMap()) {
    if (!obj instanceof Object) return obj;
    // 防止循环引用
    if (cache.get(obj)) return cache.get(obj);
    // 支持函数
    if (obj instanceof Function) {
      return function () {
        obj.apply(this, arguments);
      };
    }
    // 支持日期
    if (obj instanceof Date) return new Date(obj);
    // 支持正则对象
    if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
    // 还可以增加其他对象，比如：Map, Set等，根据情况判断增加即可，面试点到为止就可以了

    // 数组是 key 为数字素银的特殊对象
    const res = Array.isArray(obj) ? [] : {};
    // 缓存 copy 的对象，用于处理循环引用的情况
    cache.set(obj, res);

    // 针对Symbol属性
    const symKeys = Object.getOwnPropertySymbols(obj);
    if (symKeys.length) {
      symKeys.forEach((symKey) => {
        if (typeof obj[symKey] === 'object' && obj[symKey] !== null) {
          res[symKey] = deepCopy(obj[symKey]);
        } else {
          res[symKey] = obj[symKey];
        }
      });
    }

    Object.keys(obj).forEach((key) => {
      if (obj[key] instanceof Object) {
        res[key] = deepCopy(obj[key], cache);
      } else {
        res[key] = obj[key];
      }
    });

    return res;
  }

  let reg = /yideng/g;
  function cloneReg(target, isDeep) {
    var regFlag = /\w*$/;
    // target.constructor等价 RegExp 一般写库这么写
    var result = new target.constructor(target.source, regFlag.exec(target));
    if (isDeep) {
      result.lastIndex = 0;
    } else {
      result.lastIndex = target.lastIndex;
    }

    return result;
  }
  const reg2 = cloneReg(reg, true);

  const buf = Buffer.from('laoyuan');
  function cloneBuffer(buffer, isDeep) {
    if (!isDeep) return buffer.slice();
    const len = buffer.length;
    const allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined;
    const result = allocUnsafe ? allocUnsafe(len) : new buffer.constructor(len);
    return result;
  }
  const buf2 = cloneBuffer(buf, true);
  buf2.write('nodejs');
  buf2.write('22');
  console.log('buf: ', buf.toString('utf-8'));
  console.log('buf2: ', buf2.toString('utf-8'));

  // 测试
  const source = {
    name: 'Jack',
    meta: {
      age: 12,
      birth: new Date('1997-10-10'),
      ary: [1, 2, { a: 1 }],
      say() {
        console.log('Hello');
      },
    },
  };
  source.source = source;
  const newObj = deepCopy(source);
  console.log(newObj.meta.ary[2] === source.meta.ary[2]);
}

{
  const obj = { a: 1, b: 2, c: [1, 2] };
  obj.d = obj;
  function deepClone(obj) {
    return new Promise((resolve, reject) => {
      const { port1, port2 } = new MessageChannel();
      port1.postMessage(obj);

      port2.onmessage = (msg) => {
        resolve(msg.data);
      };
    });
  }

  // 可以解决 JSON.stringify(JSON.parse(obj))解决不了的循环应用问题
  deepClone(obj).then((res) => {
    console.log(res);
  });
}

{
  // 写一个异步函数 promise
  // fs.readFile(p, o, (err, data) => { })
  // let newReadFile =

  let newReadFile = () => {
    return new Promise((resolve, reject) => {
      fs.readFile((err, data) => {
        if (err) {
          reject(err);
        }
        resolve(data);
      });
    });
  };

  // 实现一下promisify
  // （promisify是node中utils模块的一个函数，他的作用是为了转换最后一个参数是回调函数的函数的promise函数，且回掉函数中有两个参数 err data）
  // node
  // Error First
  // 1. Cb 最后一个
  // 2. Cb 参数第一个err
  // promisify
  // var newReadFile = promisify(fs.readFile)
  // var promisify

  // 未使用 promisify
  fs.readFile('./index.js', (err, data) => {
    if (err) {
      console.log(err);
    }
    console.log(data.toString());
  });

  // 使用promisify
  const readFile = promisify(fs.readFile);
  readFile('./index.js')
    .then((data) => {
      console.log(data);
    })
    .catch((err) => {
      console.log(err);
    });

  // 模拟实现 promisify
  function promisify(fn) {
    if (typeof fn !== 'function') {
      throw new Error('arguments must be a function');
    }

    return function (...args) {
      return new Promise((resolve, reject) => {
        try {
          fn.call(this, ...args, (err, data) => {
            if (err) {
              reject(err);
            } else {
              resolve(data);
            }
          });
        } catch (error) {
          reject(error);
        }
      });
    };
  }
}

{
  // 实现一个 JSON.stringfy
  function stringfy(jsonObj) {
    var result = '';
    var curVal;

    if (jsonObj === null) {
      return String(jsonObj);
    }
    switch (typeof jsonObj) {
      case 'string':
        return '"' + jsonObj + '"';
      case 'number':
      case 'boolean':
        return String(jsonObj);
      case 'undefined':
      case 'function':
        return undefined;
    }

    switch (Object.prototype.toString.call(jsonObj)) {
      case '[object Array]':
        result += '[';
        for (let i = 0; i < jsonObj.length; i++) {
          const element = jsonObj[key];
          curVal = JSON.stringify(element);
          result += (curVal === undefined ? null : curVal) + ',';
        }
        if (result !== '[') {
          result = result.slice(0, -1);
        }
        result += ']';
      case '[object Date]':
        return (
          '"' + (jsonObj.toJSON ? jsonObj.toJSON() : jsonObj.toString()) + '"'
        );
      case '[object RegExp]':
        return '{}';
      case '[object Object]':
        result += '{';
        for (const key in jsonObj) {
          if (jsonObj.hasOwnProperty(key)) {
            const element = jsonObj[key];
            curVal = JSON.stringify(element);
            if (curVal !== undefined) {
              result += '"' + key + '":' + curVal + ',';
            }
          }
        }
        if (result !== '{') {
          result = result.slice(0, -1);
        }
        result += '}';

        return result;
      case '[object String]':
        return '"' + jsonObj.toString() + '"';
      case '[object Number]':
      case '[object Boolean]':
        return jsonObj.toString();
    }
  }
  let obj = {
    a: 1,
    b: 'asd',
    c: [1, 2, 3],
    d: {
      f: 1,
    },
  };
  console.log(stringfy(obj) === JSON.stringify(obj));
  console.log(stringfy(obj));
  console.log(JSON.stringify(obj));
}

{
  // 实现一个 JSON.parse()方法

  // eval实现
  function parse(jsonStr) {
    return eval('(' + jsonStr + ')');
  }

  // new Function 实现
  function parse(jsonStr) {
    return new Function('return' + jsonStr)();
  }

  var jsonStr = '{"age": 20, "name": "jack"}';
  console.log(parse(jsonStr));
}

{
  // 观察者模式是定义了对象间的一种‘一对多’的依赖关系，当一个状态发生变化时，所有依赖于它的对象都将得到通知并自动更新

  // 观察者
  class Observer {
    constructor(name) {
      this.name = name;
    }
    // 观察者默认提供一个update方法，用于更新数据
    update(info, ...args) {
      console.log(`hello, ${this.name}, 告诉你一件事: ${info}`);
    }
  }
  // 目标对象
  class Subject {
    constructor() {
      this.observerList = [];
    }
    addObserver(observer) {
      this.observerList.push(observer);
    }
    notify() {
      this.observerList.forEach((observer) => observer.update(...arguments));
    }
  }

  const Tt = new Observer('婷婷');
  const Xy = new Observer('小月');
  const sub = new Subject();
  sub.addObserver(Tt);
  sub.addObserver(Xy);
  sub.notify('今天我要干你');
  sub.notify('明天我要干你');

  /**
   * 打印结果：
   * hello, 婷婷, 告诉你一件事: 今天我要干你
   * hello, 小月, 告诉你一件事: 今天我要干你
   * hello, 婷婷, 告诉你一件事: 明天我要干你
   * hello, 小月, 告诉你一件事: 明天我要干你
   */
}

{
  // 观察者模式与发布订阅者模式的区别：
  // 观察者：由具体的发布器调度，Dep就会去调用观察者的方法，所以观察者模式的观察者与发布器之间存在依赖关系；
  // 发布订阅：由统一调度中心调用，因此发布者和订阅者不需要知道对方的存在（没有依赖关系）；
  // 二者应用场景：
  // 观察者模式：微信公众号订阅号通知、消息通知、邮件通知、vue数据双向绑定，jq的trigger事件等；
  // 发布订阅者模式：vue数据双向绑定、vue自定义事件、vue的eventBus组件通信、微信群（群是订阅者，群主是发布者，群主向群(事件中心)，发布消息，群成员能收到消息）、
}

{
  // 简单发布订阅模式
  class EventBus {
    constructor() {
      this.task = {};
    }

    // 绑定事件 将cb push到任务队列（即订阅）
    on(name, cb) {
      if (!this.task[name]) {
        this.task[name] = [];
      }

      typeof cb === 'function' && this.task[name].push(cb);
    }

    // 触发事件 拿到当前任务队列，遍历执行（即发布）
    emit(name, ...args) {
      let taskQueue = this.task[name];
      if (taskQueue && taskQueue.length > 0) {
        taskQueue.map((cb) => cb(...args));
      }
    }

    // 解除事件绑定 从当前事件队列移除当前事件 即数组删除当前元素（即取消订阅）
    off(name, cb) {
      let taskQueue = this.task[name];
      if (taskQueue && taskQueue.length > 0) {
        let idx = taskQueue.indexOf(cb);
        idx !== -1 && taskQueue.splice(idx, 1);
      }

      if (typeof cb === 'undefined') {
        this.task[name].length = 0;
      }
    }

    // 一次事件 先移除当前事件，在绑定（即订阅一次）
    once(name, cb) {
      let callback = (...args) => {
        this.off(name, callback);
        cb(...args);
      };

      typeof cb === 'function' && this.on(name, callback);
    }

    // 解除所有事件绑定（即取消所有订阅）
    offAllListener(name) {
      if (this.task[name]) {
        delete this.task[name];
      }
    }
  }

  let bus = new EventBus();
  bus.on('add', function (a, b) {
    console.log(a + b);
  });
  bus.emit('add', 10, 20);

  let obj = {
    list: [], // 可以用对象，这里用数组代替
    // 订阅
    listener: function (key, fn) {
      this.list[key] = [...(this.list[key] || []), fn];
    },
    //发布
    trigger: function () {
      let [key, ...args] = [...arguments];

      let listFns = this.list[key];
      if (listFns.length == 0 || !listFns) {
        return;
      }

      for (let itemFn of listFns) {
        itemFn.apply(this, args);
      }
    },
  };

  obj.listener('eat', (food) => {
    console.log(`晚上吃的是${food}`);
  });
  obj.listener('drink', (water) => {
    console.log(`晚上喝的是${water}`);
  });
  obj.trigger('eat', '面条');
  obj.trigger('drink', '椰子水');
}

{
  // 实现一个 compose 函数
  const compose = (...fn) => {
    if (fn.length === 0) return (v) => v;
    if (fn.length === 1) return fn[0];

    return fn.reduce(
      (pre, cur) =>
        (...args) =>
          pre(cur(...args))
    );

    // return fn.reduce((pre, cur) => {
    //   return (...args) => {
    //     return pre(cur(...args));
    //   };
    // });
  };

  function fn1(x) {
    return x + 1;
  }
  function fn2(x) {
    return x + 2;
  }
  function fn3(x) {
    return x + 3;
  }
  function fn4(x) {
    return x + 4;
  }
  const a = compose(fn1, fn2, fn3, fn4);
  console.log(a(1)); // 1+4+3+2+1=11
}

{
  // setTimeout 模拟实现 setInterval
  const mtSetInterval = (cb, ms) => {
    const interval = () => {
      setTimeout(interval, ms);
      cb();
    };

    setTimeout(interval, ms);
  };

  mtSetInterval(() => console.log(111), 1000);

  // 扩展思考：为什么要用 settimeout 模拟实现 setinterval？setinterval 的缺陷是什么？

  // setInterval 有两个缺点：
  // 使用 setInterval 时，某些间隔会被跳过；
  // 可能多个定时器会连续执行；

  // 每个 setTimeout 产生的任务会直接 push 到任务队列中；而 setInterval 在每次把任务 push 到任务队列前，都要进行一下判断(看上次的任务是否仍在队列中，如果有则不添加，没有则添加)。
}

{
  // setinterval 模拟实现 settimeout
  const mySettimeout = (cb, ms) => {
    const timer = setInterval(() => {
      cb();
      clearInterval(timer);
    }, ms);
  };

  mySettimeout(() => console.log(111), 1000);
}

{
  // 将虚拟 Dom 转化为真实 Dom
  // {
  //   tag: 'DIV',
  //   attrs:{
  //     id:'app'
  //   },
  //   children: [
  //     {
  //       tag: 'SPAN',
  //       children: [
  //         { tag: 'A', children: [] }
  //       ]
  //     },
  //     {
  //       tag: 'SPAN',
  //       children: [
  //         { tag: 'A', children: [] },
  //         { tag: 'A', children: [] }
  //       ]
  //     }
  //   ]
  // }
  // 把上诉虚拟Dom转化成下方真实Dom
  // <div id="app">
  //   <span>
  //     <a></a>
  //   </span>
  //   <span>
  //     <a></a>
  //     <a></a>
  //   </span>
  // </div>

  function _render(vnode) {
    // 如果是数字类型转化为字符串
    if (typeof vnode === 'number') {
      vnode = String(vnode);
    }
    // 字符串类型直接就是文本节点
    if (typeof vnode === 'string') {
      return document.createTextNode(vnode);
    }
    // 普通dom
    const dom = document.createElement(vnode.tag);
    if (vnode.attrs) {
      // 遍历属性
      Object.keys(vnode.attrs).forEach((key) => {
        const value = vnode.attrs[key];
        dom.setAttribute(key, value);
      });
    }

    // 子数组递归处理
    vnode.children.forEach((child) => dom.appendChild(_render(child)));

    return dom;
  }

  const vnode = {
    tag: 'DIV',
    attrs: {
      id: 'app',
    },
    children: [
      {
        tag: 'SPAN',
        children: [{ tag: 'A', children: [] }],
      },
      {
        tag: 'SPAN',
        children: [
          { tag: 'A', children: [] },
          { tag: 'A', children: [] },
        ],
      },
    ],
  };

  console.log(_render(vnode));
}

{
  // 请实现 DOM2JSON 一个函数，可以把一个 DOM 节点输出 JSON 的格式
  // <div>
  //   <span>
  //     <a></a>
  //   </span>
  //   <span>
  //     <a></a>
  //     <a></a>
  //   </span>
  // </div>
  // 把上诉dom结构转成下面的JSON格式
  // {
  //   tag: 'DIV',
  //   children: [
  //     {
  //       tag: 'SPAN',
  //       children: [
  //         { tag: 'A', children: [] }
  //       ]
  //     },
  //     {
  //       tag: 'SPAN',
  //       children: [
  //         { tag: 'A', children: [] },
  //         { tag: 'A', children: [] }
  //       ]
  //     }
  //   ]
  // }
  function dom2Json(domtree) {
    let obj = {};
    obj.tag = domtree.tagName;

    obj.children = [];
    domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));

    return obj;
  }
}

{
  // 实现 LazyMan
  //
  // 实现一个LazyMan，可以按照以下方式调用:
  // LazyMan(“Hank”)输出:
  // Hi! This is Hank!
  //
  // LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
  // Hi! This is Hank!
  // //等待10秒..
  // Wake up after 10
  // Eat dinner~
  //
  // LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
  // Hi This is Hank!
  // Eat dinner~
  // Eat supper~
  //
  // LazyMan(“Hank”).eat(“supper”).sleepFirst(5)输出
  // //等待5秒
  // Wake up after 5
  // Hi This is Hank!
  // Eat supper

  class _LazyMan {
    constructor(name) {
      this.tasks = [];
      const task = () => {
        console.log(`Hi! This is ${name}!`);
        this.next();
      };
      this.tasks.push(task);

      setTimeout(() => {
        // 把this.next()放到调用栈清空之后执行
        this.next();
      }, 0);
    }
    next() {
      const task = this.tasks.shift(); // 取出队列中第一个任务执行
      task && task();
    }
    sleep(time) {
      this._sleepWrapper(time, false);
      return this;
    }
    sleepFirst(time) {
      this._sleepWrapper(time, true);
      return this;
    }
    _sleepWrapper(time, first) {
      const task = () => {
        setTimeout(() => {
          console.log(`Wake up after ${time}`);
          this.next();
        }, time * 1000);
      };
      if (first) {
        this.tasks.unshift(task); //  放到队列头部
      } else {
        this.tasks.push(task); // 放到队列尾部
      }
    }
    eat(name) {
      const task = () => {
        console.log(`Eat ${name}`);
        this.next();
      };
      this.tasks.push(task);
      return this;
    }
  }

  function LazyMan(name) {
    return new _LazyMan(name);
  }
  // LazyMan('Hank');
  // LazyMan('Hank').sleep(10).eat('dinner');
  // LazyMan('Hank').eat('dinner').eat('supper');
  // LazyMan('Hank').eat('supper').sleepFirst(5);
}

{
  // 设计一个LRU缓存机制，它只是以下操作：获取数据get和写入数据put，
  // 获取数据get(key) 如果key存在缓存中，则获取key对应的值（总是正数），否则返回-1
  // 写入数据put(key, value) 如果key存在，变更其数值，如果不存在，直接写入，当缓存量达到上限时，它应该在写入新数据之前，删除最久未使用的数据，从而为新的数据留出空间
  // 是否可以在O(1)时间复杂度内完成？？？

  class LRUCache {
    constructor(capacity) {
      this.capacity = capacity; // 最大容量
      // Map对象回更具对象元素中插入的顺序来进行
      // 新添加的元素会被插入搭配map的末尾，整个栈倒序查看
      this.secretKey = new Map();
    }

    get(key) {
      if (this.secretKey.has(key)) {
        // 先删除 在存 保证 用过的是最新的一个
        const tempValue = this.secretKey.get(key);
        this.secretKey.delete(key);
        this.secretKey.set(key, tempValue);
        return tempValue;
      }
      return -1;
    }
    put(key, value) {
      if (this.secretKey.has(key)) {
        // key存在 直接修改
        this.secretKey.delete(key);
        this.secretKey.set(key, value);
      } else if (this.secretKey.size < this.capacity) {
        // key不存在 cache容量未满
        this.secretKey.set(key, value);
      } else {
        // 添加新key 删除旧的key
        this.secretKey.set(key, value);

        // 删除map的第一个元素，即为最长未使用的
        this.secretKey.delete(this.secretKey.keys().next().value);
      }
    }
  }

  let cache = new LRUCache(2);
  cache.put(1, 1);
  cache.put(2, 2);
  console.log('cache.get(1)', cache.get(1)); // 返回  1
  cache.put(3, 3); // 该操作会使得密钥 2 作废
  console.log('cache.get(2)', cache.get(2)); // 返回 -1 (未找到)
  cache.put(4, 4); // 该操作会使得密钥 1 作废
  console.log('cache.get(1)', cache.get(1)); // 返回 -1 (未找到)
  console.log('cache.get(3)', cache.get(3)); // 返回  3
  console.log('cache.get(4)', cache.get(4)); // 返回  4
}

{
  // 实现一个add方法完成两个大数相加;
  function add(a, b) {
    // 先转化成字符串
    a = a + '';
    b = b + '';
    // 取两个数字的最大长度
    let maxLength = Math.max(a.length, b.length);
    // 用0去补齐长度
    a = a.padStart(maxLength, 0); // "0009007199254740991"
    b = b.padStart(maxLength, 0); // "1234567899999999999"

    let t = 0; // 定义加法过程中需要用到的变量
    let f = 0; // 进位
    let sum = '';
    for (let i = maxLength - 1; i >= 0; i--) {
      t = parseInt(a[i]) + parseInt(b[i]) + f;
      f = Math.floor(t / 10);
      sum = (t % 10) + sum;
    }
    if (f !== 0) {
      sum = '' + f + sum;
    }
    return sum;
  }

  let a = '9007199254740991';
  let b = '1234567899999999999';
  console.log(add(a, b));
  // 还可以借助第三方库bignumber.js实现
}

{
  // 实现一个对象的 flatten 方法
  const obj = {
    a: {
      b: 1,
      c: 2,
      d: { e: 5 },
    },
    b: [1, 3, { a: 2, b: 3 }],
    c: 3,
  };

  // flatten(obj); // 结果返回如下
  // {
  //  'a.b': 1,
  //  'a.c': 2,
  //  'a.d.e': 5,
  //  'b[0]': 1,
  //  'b[1]': 3,
  //  'b[2].a': 2,
  //  'b[2].b': 3
  //   c: 3
  // }

  function isObject(val) {
    return typeof val === 'object' && val !== null;
  }

  function flatten(obj) {
    if (!isObject(obj)) {
      return;
    }

    let res = {};
    const dfs = (cur, prefix) => {
      if (isObject(cur)) {
        if (Array.isArray(cur)) {
          cur.forEach((item, index) => {
            dfs(item, `${prefix}[${index}]`);
          });
        } else {
          for (let k in cur) {
            dfs(cur[k], `${prefix}${prefix ? '.' : ''}${k}`);
          }
        }
      } else {
        res[prefix] = cur;
      }
    };
    dfs(obj, '');

    return res;
  }

  console.log(flatten(obj));
}

{
  // 列表转成树形结构

  let list = [
    { id: 1, name: '部门1', pid: 0 },
    { id: 2, name: '部门2', pid: 1 },
    { id: 3, name: '部门3', pid: 1 },
    { id: 4, name: '部门4', pid: 3 },
    { id: 5, name: '部门5', pid: 4 },
  ];

  // [
  //   {
  //     id: 1,
  //     name: '部门1',
  //     pid: 0,
  //     children: [
  //       {
  //         id: 2,
  //         name: '部门2',
  //         pid: 1,
  //         children: [],
  //       },
  //       {
  //         id: 3,
  //         name: '部门3',
  //         pid: 1,
  //         children: [
  //           // 结果 ,,,
  //         ],
  //       },
  //     ],
  //   },
  // ];

  // 法一 递归
  const listToTree = (data, pid) => {
    const result = [];
    function deepDFS(data, result, pid) {
      for (const item of data) {
        if (item.pid === pid) {
          const newItem = {
            ...item,
            children: [],
          };
          result.push(newItem);
          deepDFS(data, newItem.children, item.id);
        }
      }
    }
    deepDFS(data, result, pid);
    return result;
  };
  // const getChildren = (data, result, pid) => {
  //   for (const item of data) {
  //     if (item.pid === pid) {
  //       const newItem = {
  //         ...item,
  //         children: [],
  //       };
  //       result.push(newItem);
  //       getChildren(data, newItem.children, item.id);
  //     }
  //   }
  // };
  // const listToTree = (data, pid) => {
  //   const result = [];
  //   getChildren(data, result, pid);
  //   return result;
  // };
  // 时间复杂度为O(2^n)

  // 法二
  // 先把数据转成Map去存储，之后遍历的同时借助对象的引用，直接从Map找对应的数据做存储
  // function listToTree(data) {
  //   const result = [];
  //   const tempMap = {};

  //   for (const item of data) {
  //     tempMap[item.id] = item;
  //   }

  //   for (const item of data) {
  //     const id = item.id;
  //     const pid = item.pid;
  //     const treeItem = tempMap[id];

  //     if (pid === 0) {
  //       result.push(treeItem);
  //     } else {
  //       if (!tempMap[pid].children) {
  //         tempMap[pid].children = [];
  //       }
  //       tempMap[pid].children.push(treeItem);
  //     }
  //   }
  //   return result;
  // }
  function listToTree(list) {
    let map = {};
    list.forEach((item) => {
      if (!map[item.id]) {
        map[item.id] = item; // 这里是关键，将list里面的每一个item的地址，复制到map上，注意这里是饮用哦哦
      }
    });

    list.forEach((item) => {
      if (item.pid !== 0) {
        map[item.pid].childres
          ? map[item.pid].childres.push(item)
          : (map[item.pid].childres = [item]);
      }
    });

    // 一直操作的map对象，为什么最后list的数据结构变了？
    // 这里主要是因为上面的一步，将每个item的地址复制到了map上
    // 操作map，list里面对应的地址存放的数据跟着变了
    return list.filter((item) => item.pid === 0);
  }
  // 从上面的代码我们分析，有两次循环，该实现的时间复杂度为O(2n)，需要一个Map把数据存储起来，空间复杂度O(n)

  // 法三
  // 主要思路也是先把数据转成Map去存储，之后遍历的同时借助对象的引用，直接从Map找对应的数据做存储。
  // 不同点在遍历的时候即做Map存储,有找对应关系。性能会更好。
  function listToTree(data) {
    const result = [];
    const tempMap = {};

    for (const item of data) {
      const id = item.id;
      const pid = item.pid;

      if (!tempMap[id]) {
        tempMap[id] = {
          children: [],
        };
      }

      tempMap[id] = {
        ...item,
        children: tempMap[id].children,
      };

      const treeItem = tempMap[id];
      if (pid === 0) {
        result.push(treeItem);
      } else {
        if (!tempMap[pid].children) {
          tempMap[pid].children = [];
        }
        tempMap[pid].children.push(treeItem);
      }
    }
    return result;
  }

  console.log(listToTree(list));
}

{
  // 树形结构转成列表

  // [
  //   {
  //       id: 1,
  //       text: '节点1',
  //       parentId: 0,
  //       children: [
  //           {
  //               id:2,
  //               text: '节点1_1',
  //               parentId:1
  //           }
  //       ]
  //   }
  // ]
  // 转成
  // [
  //     {
  //         id: 1,
  //         text: '节点1',
  //         parentId: 0 //这里用0表示为顶级节点
  //     },
  //     {
  //         id: 2,
  //         text: '节点1_1',
  //         parentId: 1 //通过这个字段来确定子父级
  //     }
  //     ...
  // ]

  const treeData = [
    {
      id: 'p1',
      title: '广东',
      children: [
        {
          id: 'p1-1',
          title: '广州',
        },
      ],
    },
    {
      id: 'p2',
      title: '四川',
      children: [
        {
          id: 'p2-1',
          title: '成都',
          children: [
            {
              id: 'p2-1-1',
              title: '高新区',
            },
          ],
        },
        {
          id: 'p2-2',
          title: '德阳',
        },
        {
          id: 'p2-3',
          title: '绵阳',
        },
      ],
    },
  ];

  // [
  //   {
  //       id: 1,
  //       text: '节点1',
  //       parentId: 0,
  //       children: [
  //           {
  //               id:2,
  //               text: '节点1_1',
  //               parentId:1
  //           }
  //       ]
  //   }
  // ]
  // 转成
  // [
  //     {
  //         id: 1,
  //         text: '节点1',
  //         parentId: 0 //这里用0表示为顶级节点
  //     },
  //     {
  //         id: 2,
  //         text: '节点1_1',
  //         parentId: 1 //通过这个字段来确定子父级
  //     }
  //     ...
  // ]

  // 深度遍历
  function treeToList(data) {
    const res = [];

    const dfs = (treeList) => {
      treeList.forEach((item) => {
        if (item.children) {
          dfs(item.children);
          delete item.children;
        }
        res.push(item);
      });
    };
    dfs(data);

    return res;
  }

  // 广度遍历
  function treeToList(data) {
    const result = [];
    const stack = [...data]; // 这个树形结构如果是数组直接赋值，如果是对象处理成数组，因为要用队列的形式处理
    while (stack.length > 0) {
      const task = stack.shift();
      if (task.children) {
        task.children.map((item) => stack.push(item));
        delete task.children;
      }
      result.push(task);
    }
    return result;
  }
}

{
  // 版本号排序
  // 题目描述:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。
  // 现在需要对其进行排序，排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']
  function sortVersion(arr) {
    arr.sort((a, b) => {
      const arr1 = a.split('.');
      const arr2 = b.split('.');
      let i = 0;

      while (true) {
        let s1 = arr1[i];
        let s2 = arr2[i];
        i++;

        if (s1 === undefined || s2 === undefined) {
          // 4.3.5  4.3.5.5 这种比较
          return arr2.length - arr1.length;
        }

        if (s1 === s2) {
          continue;
        }

        return s2 - s1;
      }
    });

    return arr;
  }

  function sortVersion(list) {
    return list.sort((a, b) => {
      let aa = a.split('.');
      let bb = b.split('.');
      let length = aa.length > bb.length ? aa.length : bb.length;
      for (var i = 0; i < length; i++) {
        let x = aa[i] || 0;
        let y = bb[i] || 0;

        if (x - y !== 0) return y - x; // 等价于下面写法

        // if (x === y) continue;
        // return y - x;
      }
    });
  }
  sortVersion(['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']);
}

{
  // 分片思想解决大数据量渲染问题
  let ul = document.getElementById('container');
  // 插入十万条数据
  let total = 100000;
  // 一次插入 20 条
  let once = 20;
  //每条记录的索引
  let index = 0;
  //循环加载数据
  function loop(curTotal, curIndex) {
    if (curTotal <= 0) return false;

    let pageCount = Math.min(curTotal, once);
    window.requestAnimationFrame(() => {
      let fragment = document.createDocumentFragment();
      for (let i = 0; i < pageCount; i++) {
        let li = document.createElement('li');
        li.innerHTML = curIndex + i + ' : ' + ~~(Math.random() * total);
        fragment.appendChild(li);
      }
      ul.appendChild(fragment);
      loop(curTotal - pageCount, curIndex + pageCount);
    });
  }
  loop(total, index);
  // 对于大数据量的简单 dom 结构渲染可以用分片思想解决 如果是复杂的 dom 结构渲染可以使用虚拟列表
}

{
  // 给你一个对象，统计一下它的层数
  const obj = {
    a: { b: [1] },
    c: { d: { e: { f: 1 } } },
  };

  console.log(loopGetLevel(obj)); // 4

  function loopGetLevel(obj) {
    let res = 1;

    function computedLevel(obj, level = 0) {
      if (typeof obj === 'object') {
        for (const key in obj) {
          if (typeof obj[key] === 'object') {
            computedLevel(obj[key], level + 1);
          } else {
            res = level + 1 > res ? level + 1 : res;
          }
        }
      } else {
        res = level > res ? level : res;
      }
    }

    computedLevel(obj);

    return res;
  }
}

{
  // 通过立即执行函数实现 let
  var a = 1;
  (() => {
    var a = 2;
    console.log(a); // 2
  })();
  console.log(a); // 1

  // 基于 Object.defineProperty 实现 const
  // 由于 es5 环境没有 block 的概念，所以是无法百分百实现 const，
  // 只能是挂载到某个对象下，要么是全局的 window,要么就是自定义一个 object 来当容器

  function _const(data, value) {
    Object.defineProperty(window, data, {
      enumerable: false,
      configurable: false,
      get: function () {
        return value;
      },
      set: function (data) {
        throw new TypeError('Assignment to constant variable.');
      },
    });
  }
  _const('a', [1, 2]);
  a = [3, 4]; // 报错
  a.push(3);
}

{
  // 请用 es5 模拟实现 Number 的 isInteger 方法
  // Number.isInteger() 方法用来判断给定的参数是否为整数
  // isFinite 方法检测它参数的数值。如果参数是 NaN，Infinity 或者 -Infinity，会返回 false，其他返回 true。
  // Number.isInteger(Infinity) === false, 而typeof Infinity 和 Math.floor(Infinity)得到的均为 true
  // 法一 取整：Math.floor、Math.ceil、Math.round均可
  // 法二 取余

  function isInteger(value) {
    // return typeof value === 'number' && Number.isFinite(value) && Math.floor(value) === value;
    return (
      typeof value === 'number' && Number.isFinite(value) && value % 1 === 0
    );
  }

  console.log(isInteger(1221));
  console.log(isInteger(0.1221));
}

{
  // 函数重载
  function addMethod(obj, name, fn) {
    // 把前一次添加的方法存在一个临时变量
    const old = obj[name];
    // 重写obj[name]方法
    obj[name] = function (...args) {
      // fn.length 调用函数参数的个数（这里指的是searcher.find）
      if (args.length === fn.length) {
        // 调用obj[name]方法时 传入的参数跟预期的一样 直接调用方法
        return fn.apply(this, args);
      } else if (typeof old === 'function') {
        // 否则 判断old是否是函数 如果是函数 直接调用old方法
        return old.apply(this, args);
      }
    };
  }

  const searcher = {};

  addMethod(searcher, 'find', () => {
    console.log('查询所有用户');
  });

  addMethod(searcher, 'find', (name) => {
    console.log('按照姓名查询用户');
  });

  addMethod(searcher, 'find', (firstName, lastName) => {
    console.log('按照姓和名查询用户');
  });

  searcher.find();
  searcher.find('name');
  searcher.find('firstName', 'lastName');
}
